package neuron.file;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import neuron.Coord;
import neuron.Dendrite;
import neuron.Network;
import neuron.Neuron;
import neuron.Segment;
import neuron.Soma;

import common.io.CSVReader;
import common.io.Files;

public class NetmorphLoader {
	
	public static class Node {
	    public long index, label;
	    public char type; // 'a' = axon, 'd' = dendrite
	    public double[] location;
	    public long somaLabel;
	    public long parentLabel = -1;
	    public Node next[];
	    
	    public Node(List<String> data, boolean isRoot) {
	        index = Long.parseLong(data.get(0));
	        label = Long.parseLong(data.get(1));
	        type = data.get(2).charAt(0);
	        location = new double[3];
	        location[0] = Double.parseDouble(data.get(3));
	        location[1] = Double.parseDouble(data.get(4));
	        location[2] = Double.parseDouble(data.get(5));
	        somaLabel = Long.parseLong(data.get(6));
	        if (!isRoot) {
	            parentLabel = Long.parseLong(data.get(7));
	        }
	        next = new Node[2];
	    }
	    
	    public Coord asCoordinate()
	    {
	    	return new Coord(location[0], location[1], location[2], 1.0);
	    }
	    
	    public void addChild(Node n) throws IndexOutOfBoundsException
	    {
	        if (next[0] == null) { next[0] = n; return; }
	        if (next[1] == null) { next[1] = n; return; }
	        throw new IndexOutOfBoundsException("More than 2 childs! (=tri-furbication?)");  
	    }
	}

	
    List<Neuron> neurons = new ArrayList<Neuron>();

    Map<Long,Neuron> neuronMap = new HashMap<Long, Neuron>();

    List<Node> roots = new ArrayList<Node>();
    List<Node> nodes = new ArrayList<Node>();
    Map<Long,Node> nodeMap = new HashMap<Long, Node>(10000);

    OctaveParser stats = null;

	 /**
     * Load netmorph network.
     * @param f - .neurons file, e.g. config20100101_1212_net.txt.neurons
     */
    public static Network load(File f)  throws FileNotFoundException, IOException, ParseException
    {
    	// load file
    	NetmorphLoader ldr = new NetmorphLoader(f);
    	Network net = new Network();
    	net.addAll(ldr.neurons);
    	for (Neuron n : ldr.neurons) n.setNetwork(net);
    	
    	// drop node references from neuron 
    	for (Neuron n : ldr.neurons) {
    		n.removeMetadata("RootList");
    		n.removeMetadata("NodeList");
    	}
    	
    	// clear data (try to free memory faster..)
    	ldr.neurons.clear();
    	ldr.neuronMap.clear();
    	ldr.roots.clear();
    	ldr.nodes.clear();
    	ldr.nodeMap.clear();
    	ldr = null;
    	
    	return net;
    }

    
    /**
     * Load netmorph network. 
     * @param f - .neurons file, e.g. config20100101_1212_net.txt.neurons
     */
    private NetmorphLoader(File f) throws FileNotFoundException, IOException 
    {
    	// Parse basename
    	if (f == null) throw new FileNotFoundException();
    	String path = f.getAbsolutePath();
    	if (!path.endsWith(".neurons")) {
    		throw new FileNotFoundException("Given file is not .neurons!");
    	}
    	String basename = path.substring(0, path.indexOf(".neurons"));

    	readNeurons(basename);
    	readRootNodes(basename);
    	readContinuationNodes(basename);
    	readBifurcationNodes(basename);
    	readGrowthConeNodes(basename);
        readStatistics(basename);

        // connect nodes
        for (Node n : nodes) {
            if (n.parentLabel == -1) continue; // no parent (=root)
            nodeMap.get(n.parentLabel).addChild(n);
        }
        
        // construct instances of Segment from nodes. 
        for (Neuron n : neurons) {
        	List<Node> rootList = (List<Node>) n.getMetadata("RootList");
        	Dendrite longest = null;
        	double longestLen = Double.MIN_VALUE;
        	
        	// construct basal dendrites from root nodes
        	for (Node root : rootList) {
        		Segment s = parseSegment(root);
        		Dendrite d = new Dendrite(s);
//        		System.out.println("Neuron " + n + " ADD BASAL " + d);
        		if (root.type == 'd') {
        			n.addBasalDendrite(d);
            		double maxPathLen = Segment.pathLength(s.maxLengthPath());
            		if (maxPathLen > longestLen) {
            			longest = d;
            			longestLen = maxPathLen;
            		}
        		} else {
        			n.setAxon(d);
        		}
//        		System.out.println("-- straightness: " + d.straightness() + ";; max path: " + maxPathLen + ", " + n + ", " + d + "");
        	}
        	
        	// try to detect apical dendrite
                /**
                 * FIXME 03-10-2010: remove this when new netmorph version comes out with support for 'apical' labeling
                 */
        	if (longest != null) {
  //      		System.out.println("Neuron " + n + n.statistics().basalTermSegCountDist());
    //    		System.out.println("Neuron " + n + n.statistics().basalDendriteLengthDist());
      //  		System.out.println("Neuron " + n + " REMOVE BASAL " + longest);
        		n.removeBasalDendrite(longest);
        //		System.out.println("Neuron " + n + " ADD APICAL " + longest);
        		n.setApicalDendrite(longest);
        //		System.out.println("Neuron " + n + n.statistics().basalTermSegCountDist());
        //		System.out.println("Neuron " + n + n.statistics().basalDendriteLengthDist());
        	}
        }

    }
    
    /**
     * Parse segment starting from node <start>
     * 
     */
    private Segment parseSegment(Node start)
    {
    	Segment s = new Segment();
    	Node cur = start;
    	while (true) {
    		Coord p = cur.asCoordinate();
    		s.addPoint(p);
    		
    		if (cur.next[0] == null) { // terminal
    			break;
    		}
    		// branch?
    		else if (cur.next[1] != null) {
    			for (Node n : cur.next) {
    				if (n != null) s.addBranch(parseSegment(n));
    			}
    			break;
    		} 
    		// cont
    		else {
    			cur = cur.next[0];
    		}
    	}
    	return s;
    }
    
    private void readNeurons(String basename) throws FileNotFoundException, IOException
    {
    	// Read .neurons
        File neuronFile = new File(basename + ".neurons");
        CSVReader neuronReader = Files.readCSV(neuronFile);
        try {
	        while(true) {
	            List<String> line = neuronReader.readLine();
	            if (line == null) break;
	            Neuron n = parseNeuron(line);
	            neurons.add(n);
	            neuronMap.put((Long)n.getMetadata("Label"), n);
	        }
        } finally {
        	neuronReader.close();
        }
    }
    
    private void readRootNodes(String basename) throws FileNotFoundException, IOException
    {
        // Read .rootnodes
        File rootFile = new File(basename + ".rootnodes");
        CSVReader rootReader = Files.readCSV(rootFile);
        try {
	        while(true) {
	            List<String> line = rootReader.readLine();
	            if (line == null) break;
	            Node n = new Node(line, true);
	            roots.add(n);
	            nodeMap.put(n.index, n);
	            addNode(n.somaLabel, n);
	            addRootNode(n.somaLabel, n);          
	        }
        } finally {
        	rootReader.close();
        }
    }
    
    private void readContinuationNodes(String basename) throws FileNotFoundException, IOException
    {
        // Read .continuationnodes
        File contFile = new File(basename + ".continuationnodes");
        CSVReader contReader = Files.readCSV(contFile);
        try {
	        while(true) {
	            List<String> line = contReader.readLine();
	            if (line == null) break;
	            Node n = new Node(line, false);
	            nodes.add(n);
	            nodeMap.put(n.index, n);
	            addNode(n.somaLabel, n);
	        }
        } finally {
        	contReader.close();
        }
    }

    private void readBifurcationNodes(String basename) throws FileNotFoundException, IOException
    {
        // Read .bifurcationnodes
        File bifurFile = new File(basename + ".bifurcationnodes");
        CSVReader bifurReader = Files.readCSV(bifurFile);
        try {
	        while(true) {
	            List<String> line = bifurReader.readLine();
	            if (line == null) break;
	            Node n = new Node(line, false);
	            nodes.add(n);
	            nodeMap.put(n.index, n);
	            addNode(n.somaLabel, n);
	        }
        } finally {
        	bifurReader.close();
        }
    }

    private void readGrowthConeNodes(String basename) throws FileNotFoundException, IOException
    {
        // Read .growthcones
        File growthFile = new File(basename + ".growthcones");
        CSVReader growthReader = Files.readCSV(growthFile);
        try {
	        while(true) {
	            List<String> line = growthReader.readLine();
	            if (line == null) break;
	            Node n = new Node(line, false);
	            nodes.add(n);
	            nodeMap.put(n.index, n);
	            addNode(n.somaLabel, n);
	        }
        } finally {
        	growthReader.close();
        }
    }

    private void readStatistics(String basename) throws FileNotFoundException, IOException
    {
        // Read _stats.m
        File f = new File(basename.replaceAll("_net.txt", "_stats.m"));
        if (!f.exists() || !f.canRead()) return;

        stats = new OctaveParser(f);
    }

    /**
     * Get statistics matrix from _stats.m file
     * @param Statistics name
     * @return list of rows, each containing 5 doubles (n, mean, stdev, min, max)
     */
    public List<double[]> getStatsMatrix(String name)
    {
        if (stats == null) return null;
        return stats.getMatrix(name);
    }
    
    /**
     * Parse neuron data "124323141,21421412,i,n,12.0213132,123.1233213,213.12312321,.."
     * @param data
     * @return
     */
    private Neuron parseNeuron(List<String> data)
    {
    	// Index
    	long index = Long.parseLong(data.get(0));
    	
    	// Label or ID of neuron
        long label = Long.parseLong(data.get(1));
        
        char type = data.get(2).charAt(0); // 'i' = interneuron
        char region = data.get(3).charAt(0); // 'n' = net
        
        // soma is point / sphere
        Coord somaCenter = new Coord(data.get(4), data.get(5), data.get(6), "0.0");
        Soma soma = new Soma(somaCenter);
        
        Neuron n = new Neuron();
        n.addMetadata("Label", label);
        n.addMetadata("Index", index);
        n.addMetadata("Type", type);
        n.addMetadata("Region", region);
        n.setSoma(soma);
        n.addMetadata("NodeList", new ArrayList<Node>());
        n.addMetadata("RootList", new ArrayList<Node>());
        return n;
    }

    /**
     * Add node to neuron <somaLabel>
     * We use neuron metadata to store list of nodes.. (temporarily)
     */
    private void addNode(long somaLabel, Node n)
    {
        Neuron neur = neuronMap.get(n.somaLabel);
        ((List<Node>)neur.getMetadata("NodeList")).add(n);
    }

    /**
     * Add root node to neuron <somaLabel>
     * We use neuron metadata to store list of nodes.. (temporarily)
     */
    private void addRootNode(long somaLabel, Node n)
    {
        Neuron neur = neuronMap.get(n.somaLabel);
        ((List<Node>)neur.getMetadata("RootList")).add(n);
    }

    public static void main(String[] args) throws FileNotFoundException, IOException, ParseException {
		NetmorphLoader.load(new File("/tmp/NetmorphModelCreator/modeloutput_config_201006231203_net.txt.neurons"));
	}
}
